﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.Storage;

using Debug = Audio.Diagnostics.Debug;


namespace Audio
{
    // public delegate void AudioFrameHandler(byte[] pcm);
    public delegate void AudioFrameHandler(IPcmData pcm);
    public delegate void AudioEventHandler();

    class AudioCapture
    {
        public event AudioFrameHandler FrameArrived;
        public event AudioEventHandler Started;
        public event AudioEventHandler Stopped;
        public event AudioEventHandler ErrorOccured;

        public bool SaveAudioEnabled { get; set; } = false;

        private AudioHandler m_audioHandler = null;

        private readonly object readAudioEnabledLock = new object();
        private bool readAudioEnabled = false;
        private bool ReadAudioEnabled { 
            get 
            {
                bool r = false;
                //Debug.WriteLine("lock wait:ReadAudioEnabled");
                lock (readAudioEnabledLock)
                {
                    //Debug.WriteLine("lock enter:ReadAudioEnabled");
                    r = readAudioEnabled;
                }
                return r;
            }
            set
            {
                //Debug.WriteLine("lock wait:ReadAudioEnabled2");
                lock (readAudioEnabledLock)
                {
                    //Debug.WriteLine("lock enter:ReadAudioEnabled2");
                    if (readAudioEnabled != value)
                    {
                        readAudioEnabled = value;
                    }
                }
            }
        }

        //private bool CreateThreadAlready = false;
        //private Thread FetchAudioThread = null;
        //private Semaphore StartCaptureSemphore = null;
        private bool StartAlready = false;
#if false
        private List<byte> m_buffer = null;
        private List<byte> m_unconvertedBuffer = null;
#endif

        public AudioCapture()
        {

        }

        ~AudioCapture()
        {
            Stop();
            //DisposeThread();
        }

        public async void Start()
        {
            //Debug.WriteLine($"Audio.Capture.Start, Started:{StartAlready}, ThreadCreated:{CreateThreadAlready}");
            //if (!CreateThreadAlready)
            //{
            //    StartCaptureSemphore = new Semaphore(0, 1);
            //    CreateThreadAlready = true;
            //    CreateThread();
            //}

            Debug.WriteLine($"AudioCapture.Start, Started:{StartAlready}");

            //if (!StartAlready)
            //{
                //StartAlready = true;
                //StartCaptureSemphore.Release();
                //await Task.Run(async() => {
                //    try
                //    {
                //        var ret = await LaunchAudioGraphToFetch().ConfigureAwait(false);
                //        if (!ret) ErrorOccured?.Invoke();
                //    }
                //    catch
                //    {
                //        ErrorOccured?.Invoke();
                //    }
                //    //StartAlready = false;
                //    //GC.Collect();
                //}).ConfigureAwait(false);

                try
                {
                    var ret = await LaunchAudioGraphToFetch().ConfigureAwait(false);
                    if (!ret) ErrorOccured?.Invoke();
                }
                catch(Exception ex)
                {
                    Debug.WriteLine($"AudioCapture.Start, ex:{ex}");
                    ErrorOccured?.Invoke();
                }
                //finally
                //{
                //    StartAlready = false;
                //}
            //}
        }

        public void Stop()
        {
            Debug.WriteLine($"Audio.Capture.Stop");
            ReadAudioEnabled = false;
            //DisposeThread();
        }

        public bool IsCapturing()
        {
            return ReadAudioEnabled;//StartAlready;
        }

        //private void CreateThread()
        //{
        //    FetchAudioThread = new Thread(async () =>
        //    {
        //        Debug.WriteLine("CreateThread created!");
        //        while (CreateThreadAlready)
        //        {
        //            StartCaptureSemphore.WaitOne();
        //            try
        //            {
        //                var ret = await LaunchAudioGraphToFetch();
        //                if (!ret) ErrorOccured?.Invoke();
        //            }
        //            catch
        //            {
        //                ErrorOccured?.Invoke();
        //            }
        //            StartAlready = false;
        //        }
        //    });
        //    FetchAudioThread.Start();
        //}

        //private void DisposeThread()
        //{
        //    CreateThreadAlready = false;
        //    StartCaptureSemphore?.Release();
        //    FetchAudioThread?.Join();
        //    FetchAudioThread = null;
        //    if (StartCaptureSemphore != null)
        //    {
        //        StartCaptureSemphore.Dispose();
        //        StartCaptureSemphore = null;
        //    }
        //}
        private IPcmData getPcmData()
        {
            return new PcmDataImpl();
        }
        private async Task<bool> LaunchAudioGraphToFetch()
        {
            if(m_audioHandler == null)
            {
                m_audioHandler = new AudioHandler();
            }


            Debug.WriteLine("AudioCapture.LaunchAudioGraphToFetch, Enter!");
            bool res = await m_audioHandler.StartAudioAsync().ConfigureAwait(false);
            //Debug.WriteLine($"AudioGraph - Start, result:{res}");
            if (!res)
            {
                Debug.WriteLine($"AudioGraph - Start Failed!");
                m_audioHandler.Dispose();
                Debug.WriteLine("AudioCapture.LaunchAudioGraphToFetch, Leave!");
                return false;
            }

            Started?.Invoke();

            //if (m_buffer == null)
            //{
            //    m_buffer = new List<byte>();
            //}

            //m_buffer.Clear();

            //Fetch audio
            ReadAudioEnabled = true;
            Debug.WriteLine("AudioGraph - FetchAudio, Enter!");

            while (ReadAudioEnabled)
            {
                if (m_audioHandler.FlushToPcmBuffer())
                {

                    FrameArrived?.Invoke(this.getPcmData());
                    
                    Debug.WriteLine("FrameArrived");
                }





            }
            Debug.WriteLine("AudioGraph - FetchAudio, Exit!");
            //Stop audio
            if (m_audioHandler != null)
            {
                m_audioHandler.StopAudio();
                m_audioHandler = null;
            }
            //if (SaveAudioEnabled)
            //{
            //    TestAudioToFile();
            //}

            Stopped?.Invoke();
            Debug.WriteLine("AudioCapture.LaunchAudioGraphToFetch, Leave!");
            return true;
        }
#if false
        private async void TestAudioToFile()
        {
            Debug.WriteLine("---------- TestAudioToFile");
            if (m_buffer == null || m_buffer.Count == 0) return;
            byte[] byteArr = m_buffer.ToArray();

            // Prepare file to save input stream
            StorageFolder folder = ApplicationData.Current.LocalFolder;
            folder = await folder.CreateFolderAsync("sounds", CreationCollisionOption.OpenIfExists);
            //StorageFolder folder = KnownFolders.PicturesLibrary;

            //store raw data
            //String rawFilename = "mvaaudiostream_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".raw";
            //Stream rawStream = await folder.OpenStreamForWriteAsync(rawFilename, CreationCollisionOption.ReplaceExisting);
            //if (rawStream == null) return;
            //rawStream.Seek(0, SeekOrigin.End);

            //await rawStream.WriteAsync(byteArr, 0, byteArr.Length);

            //rawStream.Flush();
            //rawStream.Dispose();

            //store data with header
            String filename = "TestAudio" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".wav";
            Stream stream = null;
            try
            {
                stream = await folder.OpenStreamForWriteAsync(filename, CreationCollisionOption.ReplaceExisting);
            }
            catch { }

            if (stream == null) return;

            stream.Seek(0, SeekOrigin.End);

            // Save byte array to file
            Wave.WriteHeader(stream, false, 1, 16, 16000, byteArr.Length);
            await stream.WriteAsync(byteArr, 0, byteArr.Length);

            m_buffer.Clear();
            m_buffer = null;

            stream.Flush();
            stream.Dispose();
        }


        private async void TestUnconvertedAudioToFile()
        {
            if (m_unconvertedBuffer == null || m_unconvertedBuffer.Count == 0) return;
            byte[] byteArr = m_unconvertedBuffer.ToArray();

            // Prepare file to save input stream
            //StorageFolder folder = ApplicationData.Current.LocalCacheFolder;
            StorageFolder folder = KnownFolders.PicturesLibrary;

            String rawFilename = "Unconverted_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".raw";
            Stream rawStream = await folder.OpenStreamForWriteAsync(rawFilename, CreationCollisionOption.ReplaceExisting);
            if (rawStream == null) return;
            rawStream.Seek(0, SeekOrigin.End);

            await rawStream.WriteAsync(byteArr, 0, byteArr.Length);

            rawStream.Flush();
            rawStream.Dispose();

            String filename = "Unconverted_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".wav";
            Stream stream = await folder.OpenStreamForWriteAsync(filename, CreationCollisionOption.ReplaceExisting);
            if (stream == null) return;
            stream.Seek(0, SeekOrigin.End);

            // Save byte array to file
            Wave.WriteHeader(stream, true, 1, 32, 16000, byteArr.Length);
            await stream.WriteAsync(byteArr, 0, byteArr.Length);

            m_unconvertedBuffer.Clear();
            m_unconvertedBuffer = null;

            stream.Flush();
            stream.Dispose();
        }
#endif
    }
}
